package aceim.protocol.snuk182.icq.inner.dataprocessing;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import aceim.protocol.snuk182.icq.inner.ICQConstants;
import aceim.protocol.snuk182.icq.inner.ICQException;
import aceim.protocol.snuk182.icq.inner.ICQServiceInternal;
import aceim.protocol.snuk182.icq.inner.ICQServiceResponse;
import aceim.protocol.snuk182.icq.inner.dataentity.Flap;
import aceim.protocol.snuk182.icq.inner.dataentity.ICBMMessage;
import aceim.protocol.snuk182.icq.inner.dataentity.ICQFileInfo;
import aceim.protocol.snuk182.icq.inner.dataentity.ICQOnlineInfo;
import aceim.protocol.snuk182.icq.inner.dataentity.Snac;
import aceim.protocol.snuk182.icq.inner.dataentity.TLV;
import aceim.protocol.snuk182.icq.utils.ProtocolUtils;
public class ICBMMessagingEngine {
private ICQServiceInternal service;
private MessageParser parser;
private static final DateFormat OFFLINE_DATE_FORMATTER = new SimpleDateFormat("dd MMMM yyyy, HH:mm:ss");
static final Random RANDOM = new Random();
private ScheduledFuture<?> task;
public ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(4);
public ICBMMessagingEngine(ICQServiceInternal icqServiceInternal) {
parser = new MessageParser();
this.service = icqServiceInternal;
}
public void parseMessage(Snac snac) {
if (snac.plainData == null) {
return;
}
parser.parseMessage(snac.plainData);
}
public void parsePluginMessage(Snac snac, boolean isAck) {
if (snac.plainData == null) {
return;
}
parser.parsePluginMessage(snac.plainData, isAck);
}
public void parseOfflineMessage(byte[] tailData) {
parser.parseOfflineMessage(tailData);
}
private byte[] getAnswerXStatusSuffix(String title, String value) {
byte[] header = new byte[81];
System.arraycopy(ProtocolUtils.short2ByteLE((short) 79), 0, header, 0, 2);
System.arraycopy(ICQConstants.GUID_XSTATUSMSG, 0, header, 2, 16);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0x8), 0, header, 18, 2);
System.arraycopy(ProtocolUtils.int2ByteLE(42), 0, header, 20, 4);
byte[] headerStrBytes = new String("Script Plug-in: Remote Notification Arrive").getBytes();
System.arraycopy(headerStrBytes, 0, header, 24, 42);
System.arraycopy(ProtocolUtils.int2ByteLE(0x10000), 0, header, 66, 4);
System.arraycopy(ProtocolUtils.int2ByteLE(0), 0, header, 70, 4);
System.arraycopy(ProtocolUtils.int2ByteLE(0), 0, header, 74, 4);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0), 0, header, 78, 2);
header[80] = 0;
StringBuilder reqStrBu = new StringBuilder();
reqStrBu.append("<NR><RES>");
reqStrBu.append(ProtocolUtils.xmlToParameter("<ret event='OnRemoteNotification'><srv><id>cAwaySrv</id><val srv_id='cAwaySrv'><Root><CASXtraSetAwayMessage></CASXtraSetAwayMessage><uin>"));
reqStrBu.append(service.getUn());
reqStrBu.append(ProtocolUtils.xmlToParameter("</uin><index>1</index><title>"));
reqStrBu.append(title != null ? title : "");
reqStrBu.append(ProtocolUtils.xmlToParameter("</title><desc>"));
reqStrBu.append(value != null ? value : "");
reqStrBu.append(ProtocolUtils.xmlToParameter("</desc></Root></val></srv></ret>"));
reqStrBu.append("</RES></NR>");
String reqStr = reqStrBu.toString();
byte[] reqBytes = reqStr.getBytes();
byte[] body = new byte[8 + reqBytes.length];
System.arraycopy(ProtocolUtils.int2ByteLE(reqBytes.length + 4), 0, body, 0, 4);
System.arraycopy(ProtocolUtils.int2ByteLE(reqBytes.length), 0, body, 4, 4);
System.arraycopy(reqBytes, 0, body, 8, reqBytes.length);
byte[] total = new byte[header.length + body.length];
System.arraycopy(header, 0, total, 0, header.length);
System.arraycopy(body, 0, total, header.length, body.length);
return total;
}
private byte[] getAskXStatusSuffix(String uin) {
byte[] header = new byte[81];
System.arraycopy(ProtocolUtils.short2ByteLE((short) 79), 0, header, 0, 2);
System.arraycopy(ICQConstants.GUID_XSTATUSMSG, 0, header, 2, 16);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0x8), 0, header, 18, 2);
System.arraycopy(ProtocolUtils.int2ByteLE(42), 0, header, 20, 4);
byte[] headerStrBytes = new String("Script Plug-in: Remote Notification Arrive").getBytes();
System.arraycopy(headerStrBytes, 0, header, 24, 42);
System.arraycopy(ProtocolUtils.int2ByteLE(0x100), 0, header, 66, 4);
System.arraycopy(ProtocolUtils.int2ByteLE(0), 0, header, 70, 4);
System.arraycopy(ProtocolUtils.int2ByteLE(0), 0, header, 74, 4);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0), 0, header, 78, 2);
header[80] = 0;
String reqStr = "<N><QUERY>" + ProtocolUtils.xmlToParameter("<Q><PluginID>srvMng</PluginID></Q>") + "</QUERY><NOTIFY>" + ProtocolUtils.xmlToParameter("<srv><id>cAwaySrv</id><req><id>AwayStat</id><trans>") + 0 + ProtocolUtils.xmlToParameter("</trans><senderId>" + uin + "</senderId></req></srv>") + "</NOTIFY></N>";
byte[] reqBytes = reqStr.getBytes();
byte[] body = new byte[8 + reqBytes.length];
System.arraycopy(ProtocolUtils.int2ByteLE(reqBytes.length + 4), 0, body, 0, 4);
System.arraycopy(ProtocolUtils.int2ByteLE(reqBytes.length), 0, body, 4, 4);
System.arraycopy(reqBytes, 0, body, 8, reqBytes.length);
byte[] total = new byte[header.length + body.length];
System.arraycopy(header, 0, total, 0, header.length);
System.arraycopy(body, 0, total, header.length, body.length);
return total;
}
public void askForXStatus(String uin) {
ICBMMessage message = new ICBMMessage();
message.pluginSpecificData = getAskXStatusSuffix(message.senderId);
message.receiverId = uin;
message.text = "";
message.messageType = ICQConstants.MTYPE_PLUGIN;
sendChannel2Message(message);
}
private byte[] getPlainMessageSuffix() {
byte[] guidBytes;
try {
guidBytes = new String(ICQConstants.GUID_UTF8).getBytes("ASCII");
} catch (UnsupportedEncodingException e) {
guidBytes = new String(ICQConstants.GUID_UTF8).getBytes();
}
byte[] suffix = new byte[12 + guidBytes.length];
System.arraycopy(new byte[] { 0, 0, 0, 0 }, 0, suffix, 0, 4);
System.arraycopy(new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0 }, 0, suffix, 4, 4);
System.arraycopy(ProtocolUtils.int2ByteLE(guidBytes.length), 0, suffix, 8, 4);
System.arraycopy(guidBytes, 0, suffix, 12, guidBytes.length);
return suffix;
}
private void sendChannel2PluginMessage(ICBMMessage message) {
if (message.messageId == null) {
message.messageId = ProtocolUtils.long2ByteBE(new Random().nextLong());
}
short msgSeq = (short) 0xfffe;
Flap flap = new Flap();
flap.channel = ICQConstants.FLAP_CHANNELL_DATA;
Snac data = new Snac();
data.serviceId = ICQConstants.SNAC_FAMILY_MESSAGING;
data.subtypeId = ICQConstants.SNAC_MESSAGING_PLUGINMSG;
data.requestId = ICQConstants.SNAC_MESSAGING_PLUGINMSG;
byte[] uidBytes;
try {
uidBytes = message.receiverId.getBytes("ASCII");
} catch (UnsupportedEncodingException e) {
uidBytes = message.receiverId.getBytes();
}
byte[] block1 = new byte[13 + uidBytes.length];
System.arraycopy(message.messageId, 0, block1, 0, 8);
System.arraycopy(ProtocolUtils.short2ByteBE((short) 2), 0, block1, 8, 2);
block1[10] = (byte) uidBytes.length;
System.arraycopy(uidBytes, 0, block1, 11, uidBytes.length);
System.arraycopy(ProtocolUtils.short2ByteBE((short) 3), 0, block1, 11 + uidBytes.length, 2);
byte[] tlv2711data;
if (message.messageType == ICQConstants.MTYPE_FILEREQ) {
tlv2711data = new byte[] { 0, 2, 0, 1 };
} else {
byte[] textBytes = new byte[0]; // dummy
byte[] suffix = message.pluginSpecificData != null ? message.pluginSpecificData : getPlainMessageSuffix();
byte[] msgByteBlock = new byte[8 + textBytes.length + 1 + suffix.length];
msgByteBlock[0] = message.messageType; // !!!
msgByteBlock[1] = 0;
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0), 0, msgByteBlock, 2, 2);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0), 0, msgByteBlock, 4, 2);
System.arraycopy(ProtocolUtils.short2ByteLE((short) (textBytes.length + 1)), 0, msgByteBlock, 6, 2);
System.arraycopy(textBytes, 0, msgByteBlock, 8, textBytes.length);
msgByteBlock[8 + textBytes.length] = 0;
System.arraycopy(suffix, 0, msgByteBlock, 9 + textBytes.length, suffix.length);
tlv2711data = new byte[2 + 2 + 16 + 2 + 4 + 1 + 2 + 2 + 2 + 12 + msgByteBlock.length];
System.arraycopy(ProtocolUtils.short2ByteLE((short) 27), 0, tlv2711data, 0, 2);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 9), 0, tlv2711data, 2, 2);
byte[] zeros = new byte[16];
Arrays.fill(zeros, (byte) 0);
System.arraycopy(zeros, 0, tlv2711data, 4, 16);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0), 0, tlv2711data, 20, 2);
System.arraycopy(ProtocolUtils.int2ByteLE(1), 0, tlv2711data, 22, 4);
tlv2711data[26] = 0;
System.arraycopy(ProtocolUtils.short2ByteLE(msgSeq), 0, tlv2711data, 27, 2);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 14), 0, tlv2711data, 29, 2);
System.arraycopy(ProtocolUtils.short2ByteLE(msgSeq), 0, tlv2711data, 31, 2);
zeros = new byte[12];
Arrays.fill(zeros, (byte) 0);
System.arraycopy(zeros, 0, tlv2711data, 33, 12);
System.arraycopy(msgByteBlock, 0, tlv2711data, 45, msgByteBlock.length);
}
byte[] total = new byte[block1.length + tlv2711data.length];
System.arraycopy(block1, 0, total, 0, block1.length);
System.arraycopy(tlv2711data, 0, total, block1.length, tlv2711data.length);
data.plainData = total;
flap.data = data;
service.getRunnableService().sendToSocket(flap);
}
private void sendChannel2Message(ICBMMessage message) {
if (message.messageId == null) {
message.messageId = ProtocolUtils.long2ByteBE(new Random().nextLong());
}
short msgSeq = (short) 0xfffe;
Flap flap = new Flap();
flap.channel = ICQConstants.FLAP_CHANNELL_DATA;
Snac data = new Snac();
data.serviceId = ICQConstants.SNAC_FAMILY_MESSAGING;
data.subtypeId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER;
data.requestId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER;
/*
* TLV internalIpTLV = new TLV(); internalIpTLV.setType(0x03); byte[]
* buffer; try { buffer = InetAddress.getLocalHost().getAddress(); }
* catch (UnknownHostException e) { buffer = new byte[]{0,0,0,0}; }
* internalIpTLV.setValue(buffer);
*
* TLV portTLV = new TLV(); portTLV.setType(0x05);
* portTLV.setValue(Utils.short2ByteBE(ICQConstants.ICBM_PORT));
*/
TLV unknownA = new TLV();
unknownA.type = 0xa;
unknownA.value = new byte[] { 0, 1 };
TLV unknownF = new TLV();
unknownF.type = 0xf;
TLV msgTLV = new TLV();
msgTLV.type = 0x2711;
TLV[] tlv5content;
byte[] clsid;
if (message.messageType != ICQConstants.MTYPE_FILEREQ) {
tlv5content = new TLV[] { unknownA, unknownF, msgTLV };
clsid = ICQConstants.CLSID_ICQUTF;
byte[] textBytes;
try {
textBytes = message.text.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
textBytes = message.text.getBytes();
}
byte[] suffix = null;
switch (message.messageType) {
case ICQConstants.MTYPE_PLAIN:
suffix = getPlainMessageSuffix();
break;
case ICQConstants.MTYPE_PLUGIN:
suffix = message.pluginSpecificData;
break;
default:
suffix = new byte[0];
break;
}
byte[] msgByteBlock = new byte[8 + textBytes.length + 1 + suffix.length];
msgByteBlock[0] = message.messageType; // !!!
msgByteBlock[1] = 0;
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0), 0, msgByteBlock, 2, 2);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 1), 0, msgByteBlock, 4, 2);
System.arraycopy(ProtocolUtils.short2ByteLE((short) (textBytes.length + 1)), 0, msgByteBlock, 6, 2);
System.arraycopy(textBytes, 0, msgByteBlock, 8, textBytes.length);
msgByteBlock[8 + textBytes.length] = 0;
System.arraycopy(suffix, 0, msgByteBlock, 9 + textBytes.length, suffix.length);
byte[] tlv2711data = new byte[2 + 2 + 16 + 2 + 4 + 1 + 2 + 2 + 2 + 12 + msgByteBlock.length];
System.arraycopy(ProtocolUtils.short2ByteLE((short) 27), 0, tlv2711data, 0, 2);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 9), 0, tlv2711data, 2, 2);
byte[] zeros = new byte[16];
Arrays.fill(zeros, (byte) 0);
System.arraycopy(zeros, 0, tlv2711data, 4, 16);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 0), 0, tlv2711data, 20, 2);
System.arraycopy(ProtocolUtils.int2ByteLE(3), 0, tlv2711data, 22, 4);
tlv2711data[26] = 0;
System.arraycopy(ProtocolUtils.short2ByteLE(msgSeq), 0, tlv2711data, 27, 2);
System.arraycopy(ProtocolUtils.short2ByteLE((short) 14), 0, tlv2711data, 29, 2);
System.arraycopy(ProtocolUtils.short2ByteLE(msgSeq), 0, tlv2711data, 31, 2);
zeros = new byte[12];
Arrays.fill(zeros, (byte) 0);
System.arraycopy(zeros, 0, tlv2711data, 33, 12);
System.arraycopy(msgByteBlock, 0, tlv2711data, 45, msgByteBlock.length);
msgTLV.value = tlv2711data;
} else {
clsid = ICQConstants.CLSID_AIM_FILESEND;
if (message.rvMessageType != 0) {
tlv5content = new TLV[] { unknownA, unknownF, msgTLV };
msgTLV.value = new byte[0];
} else {
// dummy
tlv5content = new TLV[] { unknownA, unknownF, msgTLV };
msgTLV.value = new byte[0];
}
}
byte[] tlv5data = service.getDataParser().tlvs2Bytes(tlv5content);
byte[] tlv5fullData = new byte[26 + tlv5data.length];
System.arraycopy(ProtocolUtils.short2ByteBE(message.rvMessageType), 0, tlv5fullData, 0, 2);
System.arraycopy(message.messageId, 0, tlv5fullData, 2, 8);
System.arraycopy(clsid, 0, tlv5fullData, 10, 16);
System.arraycopy(tlv5data, 0, tlv5fullData, 26, tlv5data.length);
TLV ch2messageTLV = new TLV();
ch2messageTLV.type = 0x5;
ch2messageTLV.value = tlv5fullData;
TLV confirmAckTLV = new TLV();
confirmAckTLV.type = 0x3;
byte[] uidBytes;
try {
uidBytes = message.receiverId.getBytes("ASCII");
} catch (UnsupportedEncodingException e) {
uidBytes = message.receiverId.getBytes();
}
byte[] snacRawData = new byte[11 + uidBytes.length];
System.arraycopy(message.messageId, 0, snacRawData, 0, 8);
System.arraycopy(ProtocolUtils.short2ByteBE((short) 2), 0, snacRawData, 8, 2);
snacRawData[10] = (byte) message.receiverId.length();
System.arraycopy(uidBytes, 0, snacRawData, 11, uidBytes.length);
data.data = new TLV[] { ch2messageTLV, confirmAckTLV };
data.plainData = snacRawData;
flap.data = data;
service.getRunnableService().sendToSocket(flap);
}
public long sendMessage(ICBMMessage message) {
if (message == null) {
service.log("msg to send is null " + new Date());
}
if (message.messageId == null) {
message.messageId = new byte[8];
RANDOM.nextBytes(message.messageId);
}
for (ICQOnlineInfo info : service.getBuddyList().buddyInfos) {
if (info != null && info.uin.equals(message.receiverId) && info.capabilities != null) {
for (String cap : info.capabilities) {
if (cap.equals(ProtocolUtils.getHexString(ICQConstants.CLSID_ICQUTF))) {
sendChannel2Message(message);
return ProtocolUtils.bytes2LongBE(message.messageId);
}
}
break;
}
}
sendChannel1Message(message);
return ProtocolUtils.bytes2LongBE(message.messageId);
}
public void sendFileMessage(ICBMMessage message) {
for (ICQOnlineInfo info : service.getBuddyList().buddyInfos) {
if (info != null && info.uin.equals(message.receiverId)) {
for (String cap : info.capabilities) {
if (cap.equals(ProtocolUtils.getHexString(ICQConstants.CLSID_AIM_FILESEND))) {
sendChannel2Message(message);
return;
}
}
break;
}
}
}
private void sendChannel1Message(ICBMMessage message) {
Flap flap = new Flap();
flap.channel = ICQConstants.FLAP_CHANNELL_DATA;
Snac data = new Snac();
data.serviceId = ICQConstants.SNAC_FAMILY_MESSAGING;
data.subtypeId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER;
data.requestId = 0;
byte[] textBytes;
try {
textBytes = message.text.getBytes("windows-1251");
} catch (UnsupportedEncodingException e) {
textBytes = message.text.getBytes();
}
byte[] caps = new byte[] { 5, 1, 0, 1, 1 };
byte[] text = new byte[8 + textBytes.length];
text[1] = text[0] = 1;
byte[] textLength = ProtocolUtils.short2ByteBE((short) (textBytes.length + 4));
System.arraycopy(textLength, 0, text, 2, 2);
text[5] = text[4] = 0;
text[7] = text[6] = (byte) 0xff;
System.arraycopy(textBytes, 0, text, 8, textBytes.length);
TLV msgTLV = new TLV();
msgTLV.type = 2;
byte[] msgTLVBytes = new byte[caps.length + text.length];
System.arraycopy(caps, 0, msgTLVBytes, 0, caps.length);
System.arraycopy(text, 0, msgTLVBytes, caps.length, text.length);
msgTLV.value = msgTLVBytes;
TLV confirmAckTLV = new TLV();
confirmAckTLV.type = 0x3;
TLV storeMsgTLV = new TLV();
storeMsgTLV.type = 0x6;
byte[] uidBytes;
try {
uidBytes = message.receiverId.getBytes("ASCII");
} catch (UnsupportedEncodingException e) {
uidBytes = message.receiverId.getBytes();
}
byte[] snacRawData = new byte[11 + uidBytes.length];
System.arraycopy(message.messageId, 0, snacRawData, 0, 8);
System.arraycopy(ProtocolUtils.short2ByteBE((short) 1), 0, snacRawData, 8, 2);
snacRawData[10] = (byte) message.receiverId.length();
System.arraycopy(uidBytes, 0, snacRawData, 11, uidBytes.length);
data.data = new TLV[] { msgTLV, confirmAckTLV, storeMsgTLV };
data.plainData = snacRawData;
flap.data = data;
service.getRunnableService().sendToSocket(flap);
}
class MessageParser {
String encoding = "windows-1251";
void parseMessage(byte[] data) {
ICBMMessage message = new ICBMMessage();
System.arraycopy(data, 0, message.messageId, 0, 8);
message.channel = ProtocolUtils.bytes2ShortBE(data, 8);
message.receivingTime = new Date();
String uin = new String(data, 11, data[10]);
message.senderId = uin;
@SuppressWarnings("unused")
short warningLevel = ProtocolUtils.bytes2ShortBE(data, 11 + data[10]);
int fixedPartTlvCount = ProtocolUtils.bytes2ShortBE(data, 13 + data[10]);
int tlvDataLength = data.length - 15 - data[10];
byte[] tlvData = new byte[tlvDataLength];
System.arraycopy(data, 15 + data[10], tlvData, 0, tlvDataLength);
service.log("message from " + message.senderId + " id " + ProtocolUtils.getHexString(message.messageId));
ICQOnlineInfo info = new ICQOnlineInfo();
try {
TLV[] tlvs = service.getDataParser().parseTLV(tlvData);
for (int i = 0; i < fixedPartTlvCount; i++) {
service.getOnlineInfoEngine().onlineInfoTLVMap(tlvs[i], info);
}
for (int i = fixedPartTlvCount; i < tlvs.length; i++) {
messageTLVMap(tlvs[i], message);
}
service.log("message type " + message.messageType);
//if (service.checkFileTransferEngineCreated() && service.getFileTransferEngine().findMessageByMessageId(message.messageId)!=null){
if (!message.senderId.equals(service.getUn()) && message.capability != null && message.capability.equals(ProtocolUtils.getHexString(ICQConstants.CLSID_AIM_FILESEND))) {
switch (message.messageType) {
case 0:
if (!message.connectFTPeer && !message.connectFTProxy) {
service.getFileTransferEngine().getMessages().add(message);
service.getServiceResponse().respond(ICQServiceResponse.RES_FILEMESSAGE, message);
} else {
message.receiverId = message.senderId;
message.senderId = service.getUn();
service.getFileTransferEngine().redirectRequest(message);
}
break;
case 1:
service.getFileTransferEngine().transferFailed(new ICQException("Cancelled"), "", message, uin);
break;
case 2:
service.getFileTransferEngine().fireTransfer(message);
break;
}
}
} catch (ICQException e) {
service.log(e);
}
}
void parsePluginMessage(byte[] plainData, boolean isAck) {
ICBMMessage message = new ICBMMessage();
int pos = 0;
System.arraycopy(plainData, 0, message.messageId, 0, 8);
pos += 8;
short channel = ProtocolUtils.bytes2ShortBE(plainData, pos);
message.channel = channel;
pos += 2;
int uinBytes = plainData[pos];
pos += 1;
message.senderId = new String(plainData, pos, uinBytes);
pos += uinBytes;
if (isAck) {
service.getServiceResponse().respond(ICQServiceResponse.RES_MESSAGEACK, message.senderId, ProtocolUtils.bytes2LongBE(message.messageId, 0), (byte)1);
return;
}
short reasonId = ProtocolUtils.bytes2ShortBE(plainData, pos);
pos += 2;
switch (reasonId) {
case 3:
byte[] data = new byte[plainData.length - pos];
System.arraycopy(plainData, pos, data, 0, data.length);
if (data.length < 5) {
service.getFileTransferEngine().cancel(ProtocolUtils.bytes2LongBE(message.messageId, 0));
return;
} else {
channel2TextMessage(data, message, true);
}
break;
}
}
void parseOfflineMessage(byte[] tailData) {
ICBMMessage message = new ICBMMessage();
int senderUinInt = ProtocolUtils.bytes2IntLE(tailData, 0);
message.senderId = senderUinInt + "";
short year = ProtocolUtils.bytes2ShortLE(tailData, 4);
byte month = tailData[6];
byte day = tailData[7];
byte hour = tailData[8];
byte minute = tailData[9];
Calendar sendingDate = Calendar.getInstance();
sendingDate.set(year, month, day, hour, minute);
message.sendingTime = sendingDate.getTime();
message.receivingTime = new Date();
byte mType = tailData[10];
@SuppressWarnings("unused")
byte mFlag = tailData[11];
switch (mType) {
case ICQConstants.MTYPE_PLAIN:
int textLength = ProtocolUtils.bytes2ShortLE(tailData, 12) - 1;
String text;
try {
text = new String(tailData, 14, textLength, "windows-1251");
} catch (UnsupportedEncodingException e) {
text = new String(tailData, 14, textLength);
}
message.text = "(Sent at "+OFFLINE_DATE_FORMATTER.format(message.sendingTime)+") "+text;
service.log(message.senderId + " says: " + text);
if (text.length() > 0)
notifyMessageReceived(message);
break;
}
}
private void messageTLVMap(TLV tlv, ICBMMessage message) {
switch (tlv.type) {
case 0x5:
switch (message.channel) { // message channel 2
case 0x2:
if (tlv.value == null) {
return;
}
message.messageType = (byte) ProtocolUtils.bytes2ShortBE(tlv.value, 0);
// eliminate message id - 8 bytes
byte[] capability = new byte[16];
System.arraycopy(tlv.value, 10, capability, 0, 16);
String strCap = ProtocolUtils.getHexString(capability);
message.capability = strCap;
byte[] tailData = new byte[tlv.value.length - 26];
System.arraycopy(tlv.value, 26, tailData, 0, tailData.length);
try {
TLV[] channel2Tlvs = service.getDataParser().parseTLV(tailData);
for (TLV ch2tlv : channel2Tlvs) {
channel2MessageTLVMap(ch2tlv, message);
}
if (service.getFileTransferEngine().findMessageByMessageId(ProtocolUtils.bytes2LongBE(message.messageId, 0)) != null) {
message.connectFTPeer = true;
}
} catch (ICQException e) {
service.log(e);
}
break;
case 0x4: // message channel 4
channel4MessageTLVMap(tlv, message);
break;
}
break;
case 0x2:
switch (message.channel) { // message channel 1
case 0x1:
try {
TLV[] channel1Tlvs = service.getDataParser().parseTLV(tlv.value);
for (TLV ch1Tlv : channel1Tlvs) {
channel1MessageTLVMap(ch1Tlv, message);
}
} catch (ICQException e) {
service.log(e);
}
break;
}
break;
case 0x24:
service.log("0x24 unk cap - " + new String(tlv.value));
break;
case 0x13:
service.log("0x13 unk cap - " + ProtocolUtils.getHexString(tlv.value));
break;
}
}
private void channel4MessageTLVMap(TLV tlv, ICBMMessage message) {
int uinBytes = ProtocolUtils.bytes2IntLE(tlv.value, 0);
String uin = new String(uinBytes + "");
message.senderId = uin;
byte mType = tlv.value[4];
@SuppressWarnings("unused")
byte mFlag = tlv.value[5];
switch (mType) {
case ICQConstants.MTYPE_PLAIN:
int textLength = ProtocolUtils.bytes2ShortLE(tlv.value, 6) - 1;
String text;
try {
text = new String(tlv.value, 8, textLength, "ASCII");
} catch (UnsupportedEncodingException e) {
text = new String(tlv.value, 8, textLength);
}
message.text = text;
service.log(message.senderId + " says " + text);
if (text.length() > 0)
notifyMessageReceived(message);
break;
}
}
private void channel1MessageTLVMap(TLV tlv, ICBMMessage message) {
byte[] bytes = ProtocolUtils.int2ByteBE(tlv.type);
switch (bytes[2]) {
case 0x5:
byte[] capsArray = tlv.value;
service.log(ProtocolUtils.getSpacedHexString(capsArray));
/*
* if (capsArray.length > 0 && capsArray[capsArray.length - 1]
* == 6) { encoding = "UTF-16"; } else { encoding =
* "windows-1251"; }
* service.log(" channel 1 message require caps: "+encoding);
*/
break;
case 0x1:
int charsetType = ProtocolUtils.bytes2ShortBE(tlv.value, 0);
int charsetSubtype = ProtocolUtils.bytes2ShortBE(tlv.value, 2);
switch (charsetType) {
case 0:
encoding = "windows-1251";
break;
case 2:
encoding = "UTF-16";
break;
default:
encoding = "UTF-8";
break;
}
service.log("charset type " + charsetType + "| charset subtype " + charsetSubtype);
int textBytes = tlv.value.length - 4;
String text;// = Utils.getEncodedString(textBytes);
try {
// text = new String(textBytes, "UTF-16");
text = new String(tlv.value, 4, textBytes, encoding);
} catch (UnsupportedEncodingException e) {
text = new String(tlv.value, 4, textBytes);
}
message.text = text;
service.log(message.senderId + " says " + text);
if ((message.senderId.equals(service.getUn())) || text.length() > 0)
notifyMessageReceived(message);
break;
}
}
private void channel2MessageTLVMap(TLV tlv, ICBMMessage message) {
switch (tlv.type) {
case 0x2711:
if (message.capability.equals(ProtocolUtils.getHexString(ICQConstants.CLSID_SRV_RELAY))) {
channel2TextMessage(tlv.value, message, false);
}
if (message.capability.equals(ProtocolUtils.getHexString(ICQConstants.CLSID_AIM_FILESEND))) {
channel2FileMessage(tlv.value, message);
}
break;
case 0xa:
service.log("seq number " + tlv.value);
break;
case 0xf:
service.log("host check " + ProtocolUtils.getHexString(tlv.value));
break;
case 0xd:
service.log("mime " + new String(tlv.value));
break;
case 0xc:
String text;
try {
text = new String(tlv.value, "UTF-16");
} catch (UnsupportedEncodingException e) {
text = new String(tlv.value);
}
message.invitation = ProtocolUtils.xmlFromParameter(text);
service.log("invitation " + message.invitation);
break;
case 0x2:
message.rvIp = ProtocolUtils.getIPString(tlv.value);
service.log("rendezvouz ip " + message.rvIp);
break;
case 0x3:
message.internalIp = ProtocolUtils.getIPString(tlv.value);
service.log("internal ip " + message.internalIp);
break;
case 0x4:
message.externalIp = ProtocolUtils.getIPString(tlv.value);
service.log("external ip " + message.externalIp);
break;
case 0x5:
message.externalPort = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(tlv.value));
service.log("external port " + message.externalPort);
break;
case 0x10:
service.log("connect FT proxy request");
message.connectFTProxy = true;
break;
case 0x2712:
service.log("kinda encoding " + new String(tlv.value));
break;
}
if ((message.senderId.equals(service.getUn())) || (message.text != null && message.text.length() > 0 && message.capability.equals(ProtocolUtils.getHexString(ICQConstants.CLSID_SRV_RELAY)))) {
notifyMessageReceived(message);
message.receiverId = message.senderId;
// message.text = "";
message.senderId = service.getUn();
if ((service.getOnlineInfo().userStatus & ICQConstants.STATUS_INVISIBLE) < 1) {
message.messageType = ICQConstants.MTYPE_PLAIN;
sendChannel2PluginMessage(message);
}
}
}
@SuppressWarnings("unused")
private void channel2FileMessage(byte[] in, ICBMMessage message) {
if (in == null) {
return;
}
int pos = 0;
int type = ProtocolUtils.bytes2ShortBE(in, pos);
pos += 2;
int count = ProtocolUtils.bytes2ShortBE(in, pos);
pos += 2;
long filesize = ProtocolUtils.unsignedInt2Long(ProtocolUtils.bytes2IntBE(in, pos));
pos += 4;
// message.messageType = ICQConstants.MTYPE_FILEREQ;
int filenameBytes = in.length - 9;
for (int i = 0; i < count; i++) {
ICQFileInfo file = new ICQFileInfo();
file.size = filesize;
try {
file.filename = new String(in, pos, filenameBytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
service.log(e);
file.filename = new String(in, pos, filenameBytes);
}
message.files.add(file);
}
}
@SuppressWarnings("unused")
private void channel2TextMessage(byte[] data, ICBMMessage message, boolean isAck) {
short block1Length = ProtocolUtils.bytes2ShortLE(data, 0);
byte[] pluginGUID = new byte[16];
System.arraycopy(data, 4, pluginGUID, 0, 16);
String strGUID = ProtocolUtils.getHexString(pluginGUID);
if (strGUID.equals(ICQConstants.GUID_RTF_TEXT)) {
int pos = 2 + block1Length;
short block2Length = ProtocolUtils.bytes2ShortLE(data, pos);
pos = 4 + block1Length + block2Length;
byte mType = data[pos];
byte mFlag = data[pos + 1];
short statusCode = ProtocolUtils.bytes2ShortLE(data, pos + 2);
short priorityCode = ProtocolUtils.bytes2ShortLE(data, pos + 4);
int textLength = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortLE(data, pos + 6));
byte[] textBytes = null;
if (textLength > 1) {
textBytes = new byte[textLength - 1];
System.arraycopy(data, pos + 8, textBytes, 0, textLength - 1);
}
pos += (8 + textLength);
String encoding = "windows-1251";
switch (mType) {
case ICQConstants.MTYPE_ACK:
case ICQConstants.MTYPE_PLAIN:
if (!message.senderId.equals(service.getUn())) {
service.getServiceResponse().respond(ICQServiceResponse.RES_MESSAGEACK, message.senderId, ProtocolUtils.bytes2LongBE(message.messageId, 0), (byte)2);
}
int color = ProtocolUtils.bytes2IntLE(data, pos);
pos += 4;
int bgColor = ProtocolUtils.bytes2IntLE(data, pos);
pos += 4;
if (data.length >= pos + 4) {
int guidLen = ProtocolUtils.bytes2IntLE(data, pos);
pos += 4;
if (guidLen == 38) {
String guid = ProtocolUtils.getEncodedString(data, pos, guidLen);
if (guid.equals(ICQConstants.GUID_UTF8)) {
encoding = "UTF-8";
}
pos += guidLen;
}
}
break;
case ICQConstants.MTYPE_PLUGIN:
parsePluginData(message, data, pos, message.senderId);
break;
}
if (textBytes != null) {
String text = ProtocolUtils.getEncodedString(textBytes, encoding);
service.log(message.senderId + " says something " + text);
message.text = text;
} else {
message.text = "";
}
}
}
private void parsePluginData(ICBMMessage message, byte[] data, int pos, String uid) {
int headerLength = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortLE(data, pos));
byte[] pluginGuid = new byte[16];
pos += 2;
System.arraycopy(data, pos, pluginGuid, 0, 16);
if (new String(pluginGuid).equals(new String(ICQConstants.GUID_XSTATUSMSG))) {
pos += headerLength + 4;
int dataLength = ProtocolUtils.bytes2IntLE(data, pos);
pos += 4;
byte[] xmlDataBytes = new byte[dataLength];
System.arraycopy(data, pos, xmlDataBytes, 0, dataLength);
String xmlData = ProtocolUtils.getEncodedString(xmlDataBytes);
int i;
if ((i = xmlData.indexOf("<NR><RES>")) < 0) {
//service.log(message.senderId + " asks xstatus ");
service.getServiceResponse().respond(ICQServiceResponse.RES_ACCOUNT_ACTIVITY, message.senderId + " asks xstatus ");
if ((service.getOnlineInfo().userStatus & ICQConstants.STATUS_INVISIBLE) < 1){
answerOwnXStatus(message, uid);
}
return;
}
;
int j;
if ((j = xmlData.indexOf("</RES></NR>")) < 0)
return;
String s2;
if (((s2 = ProtocolUtils.xmlFromParameter(xmlData.substring(i + 9, j))).indexOf("<val srv_id='")) < 0)
return;
int j2;
int k2;
String xstatusName = "";
if ((j2 = s2.indexOf("<title>")) > 0 && (k2 = s2.indexOf("/title>")) > 0) {
xstatusName = s2.substring(j2 + 7, k2 - 1);
}
;
int l2;
int i3;
String xstatusValue = "";
if ((l2 = s2.indexOf("<desc>")) > 0 && (i3 = s2.indexOf("</desc>")) > 0) {
xstatusValue = s2.substring(l2 + 6, i3);
}
;
service.log(uid + " has xstatus: " + xstatusName + ": " + xstatusValue);
ICQOnlineInfo info = service.getBuddyList().getByUin(uid);
if (info != null) {
info.personalText = xstatusName;
info.extendedStatus = xstatusValue;
service.getServiceResponse().respond(ICQServiceResponse.RES_BUDDYSTATECHANGED, info, true);
}
}
}
}
public Flap getOfflineMessagesRequestFlap() {
Flap flap = new Flap();
flap.channel = ICQConstants.FLAP_CHANNELL_DATA;
Snac data = new Snac();
data.serviceId = ICQConstants.SNAC_FAMILY_MESSAGING;
data.subtypeId = ICQConstants.SNAC_MESSAGING_OFFLINE;
data.requestId = ICQConstants.SNAC_MESSAGING_OFFLINE;
flap.data = data;
return flap;
}
private void answerOwnXStatus(ICBMMessage askMessage, String receiverUid) {
ICBMMessage message = new ICBMMessage();
message.messageId = askMessage.messageId;
message.pluginSpecificData = getAnswerXStatusSuffix(service.getOnlineInfo().personalText, service.getOnlineInfo().extendedStatus);
message.receiverId = receiverUid;
message.text = "";
message.messageType = ICQConstants.MTYPE_PLUGIN;
sendChannel2PluginMessage(message);
}
protected void notifyMessageReceived(ICBMMessage message) {
service.getServiceResponse().respond(ICQServiceResponse.RES_MESSAGE, message);
}
public void sendFileMessageReject(ICBMMessage message) {
sendChannel2PluginMessage(message);
}
public void parseTyping(Snac snac) {
if (snac.plainData == null || snac.plainData.length == 0) {
return;
}
int pos = 10;
byte screennameLength = snac.plainData[pos];
pos++;
String screenname = new String(snac.plainData, pos, screennameLength);
pos += screennameLength;
short type = ProtocolUtils.bytes2ShortBE(snac.plainData, pos);
if (type != 0) {
service.getServiceResponse().respond(ICQServiceResponse.RES_TYPING, screenname);
}
}
public void sendTyping(String uin) {
if (task != null && !task.isCancelled()) {
task.cancel(false);
}
sendTyping(uin, false);
}
private void sendTyping(final String uin, boolean typingEnds) {
short channel = 1;
for (ICQOnlineInfo info : service.getBuddyList().buddyInfos) {
if (info != null && info.capabilities != null && info.uin.equals(uin)) {
for (String cap : info.capabilities) {
if (cap.equals(ProtocolUtils.getHexString(ICQConstants.CLSID_ICQUTF))) {
channel = 2;
break;
}
}
break;
}
}
Flap flap = new Flap();
flap.channel = ICQConstants.FLAP_CHANNELL_DATA;
Snac data = new Snac();
data.serviceId = ICQConstants.SNAC_FAMILY_MESSAGING;
data.subtypeId = ICQConstants.SNAC_MESSAGING_TYPINGNOTIFICATION;
data.requestId = ICQConstants.SNAC_MESSAGING_TYPINGNOTIFICATION;
byte[] uinBytes = uin.getBytes();
byte[] raw = new byte[13 + uinBytes.length];
int pos = 0;
System.arraycopy(ProtocolUtils.long2ByteBE(RANDOM.nextLong()), 0, raw, pos, 8);
pos += 8;
System.arraycopy(ProtocolUtils.short2ByteBE(channel), 0, raw, pos, 2);
pos += 2;
raw[pos] = (byte) uinBytes.length;
pos++;
System.arraycopy(uinBytes, 0, raw, pos, uinBytes.length);
pos += uinBytes.length;
raw[pos] = 0;
pos++;
if (typingEnds) {
raw[pos] = 0;
} else {
raw[pos] = 2;
}
pos++;
data.plainData = raw;
flap.data = data;
if (service.getRunnableService().sendToSocket(flap) && !typingEnds){
task = executor.schedule(new Runnable() {
@Override
public void run() {
sendTyping(uin, true);
}
}, 4, TimeUnit.SECONDS);
}
}
}